/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.text;

import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import java.beans.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import java.util.EventListener;
import sun.swing.SwingUtilities2;

/**
 * A default implementation of Caret.  The caret is rendered as
 * a vertical line in the color specified by the CaretColor property
 * of the associated JTextComponent.  It can blink at the rate specified
 * by the BlinkRate property.
 * <p>
 * This implementation expects two sources of asynchronous notification.
 * The timer thread fires asynchronously, and causes the caret to simply
 * repaint the most recent bounding box.  The caret also tracks change
 * as the document is modified.  Typically this will happen on the
 * event dispatch thread as a result of some mouse or keyboard event.
 * The caret behavior on both synchronous and asynchronous documents updates
 * is controlled by <code>UpdatePolicy</code> property. The repaint of the
 * new caret location will occur on the event thread in any case, as calls to
 * <code>modelToView</code> are only safe on the event thread.
 * <p>
 * The caret acts as a mouse and focus listener on the text component
 * it has been installed in, and defines the caret semantics based upon
 * those events.  The listener methods can be reimplemented to change the
 * semantics.
 * By default, the first mouse button will be used to set focus and caret
 * position.  Dragging the mouse pointer with the first mouse button will
 * sweep out a selection that is contiguous in the model.  If the associated
 * text component is editable, the caret will become visible when focus
 * is gained, and invisible when focus is lost.
 * <p>
 * The Highlighter bound to the associated text component is used to
 * render the selection by default.
 * Selection appearance can be customized by supplying a
 * painter to use for the highlights.  By default a painter is used that
 * will render a solid color as specified in the associated text component
 * in the <code>SelectionColor</code> property.  This can easily be changed
 * by reimplementing the
 * {@link #getSelectionPainter getSelectionPainter}
 * method.
 * <p>
 * A customized caret appearance can be achieved by reimplementing
 * the paint method.  If the paint method is changed, the damage method
 * should also be reimplemented to cause a repaint for the area needed
 * to render the caret.  The caret extends the Rectangle class which
 * is used to hold the bounding box for where the caret was last rendered.
 * This enables the caret to repaint in a thread-safe manner when the
 * caret moves without making a call to modelToView which is unstable
 * between model updates and view repair (i.e. the order of delivery
 * to DocumentListeners is not guaranteed).
 * <p>
 * The magic caret position is set to null when the caret position changes.
 * A timer is used to determine the new location (after the caret change).
 * When the timer fires, if the magic caret position is still null it is
 * reset to the current caret position. Any actions that change
 * the caret position and want the magic caret position to remain the
 * same, must remember the magic caret position, change the cursor, and
 * then set the magic caret position to its original value. This has the
 * benefit that only actions that want the magic caret position to persist
 * (such as open/down) need to know about it.
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans&trade;
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author Timothy Prinzing
 * @see Caret
 */
public class DefaultCaret extends Rectangle implements Caret, FocusListener, MouseListener,
    MouseMotionListener {

  /**
   * Indicates that the caret position is to be updated only when
   * document changes are performed on the Event Dispatching Thread.
   *
   * @see #setUpdatePolicy
   * @see #getUpdatePolicy
   * @since 1.5
   */
  public static final int UPDATE_WHEN_ON_EDT = 0;

  /**
   * Indicates that the caret should remain at the same
   * absolute position in the document regardless of any document
   * updates, except when the document length becomes less than
   * the current caret position due to removal. In that case the caret
   * position is adjusted to the end of the document.
   *
   * @see #setUpdatePolicy
   * @see #getUpdatePolicy
   * @since 1.5
   */
  public static final int NEVER_UPDATE = 1;

  /**
   * Indicates that the caret position is to be <b>always</b>
   * updated accordingly to the document changes regardless whether
   * the document updates are performed on the Event Dispatching Thread
   * or not.
   *
   * @see #setUpdatePolicy
   * @see #getUpdatePolicy
   * @since 1.5
   */
  public static final int ALWAYS_UPDATE = 2;

  /**
   * Constructs a default caret.
   */
  public DefaultCaret() {
  }

  /**
   * Sets the caret movement policy on the document updates. Normally
   * the caret updates its absolute position within the document on
   * insertions occurred before or at the caret position and
   * on removals before the caret position. 'Absolute position'
   * means here the position relative to the start of the document.
   * For example if
   * a character is typed within editable text component it is inserted
   * at the caret position and the caret moves to the next absolute
   * position within the document due to insertion and if
   * <code>BACKSPACE</code> is typed then caret decreases its absolute
   * position due to removal of a character before it. Sometimes
   * it may be useful to turn off the caret position updates so that
   * the caret stays at the same absolute position within the
   * document position regardless of any document updates.
   * <p>
   * The following update policies are allowed:
   * <ul>
   * <li><code>NEVER_UPDATE</code>: the caret stays at the same
   * absolute position in the document regardless of any document
   * updates, except when document length becomes less than
   * the current caret position due to removal. In that case caret
   * position is adjusted to the end of the document.
   * The caret doesn't try to keep itself visible by scrolling
   * the associated view when using this policy. </li>
   * <li><code>ALWAYS_UPDATE</code>: the caret always tracks document
   * changes. For regular changes it increases its position
   * if an insertion occurs before or at its current position,
   * and decreases position if a removal occurs before
   * its current position. For undo/redo updates it is always
   * moved to the position where update occurred. The caret
   * also tries to keep itself visible by calling
   * <code>adjustVisibility</code> method.</li>
   * <li><code>UPDATE_WHEN_ON_EDT</code>: acts like <code>ALWAYS_UPDATE</code>
   * if the document updates are performed on the Event Dispatching Thread
   * and like <code>NEVER_UPDATE</code> if updates are performed on
   * other thread. </li>
   * </ul> <p>
   * The default property value is <code>UPDATE_WHEN_ON_EDT</code>.
   *
   * @param policy one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
   * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
   * @throws IllegalArgumentException if invalid value is passed
   * @see #getUpdatePolicy
   * @see #adjustVisibility
   * @see #UPDATE_WHEN_ON_EDT
   * @see #NEVER_UPDATE
   * @see #ALWAYS_UPDATE
   * @since 1.5
   */
  public void setUpdatePolicy(int policy) {
    updatePolicy = policy;
  }

  /**
   * Gets the caret movement policy on document updates.
   *
   * @return one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
   * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
   * @see #setUpdatePolicy
   * @see #UPDATE_WHEN_ON_EDT
   * @see #NEVER_UPDATE
   * @see #ALWAYS_UPDATE
   * @since 1.5
   */
  public int getUpdatePolicy() {
    return updatePolicy;
  }

  /**
   * Gets the text editor component that this caret is
   * is bound to.
   *
   * @return the component
   */
  protected final JTextComponent getComponent() {
    return component;
  }

  /**
   * Cause the caret to be painted.  The repaint
   * area is the bounding box of the caret (i.e.
   * the caret rectangle or <em>this</em>).
   * <p>
   * This method is thread safe, although most Swing methods
   * are not. Please see
   * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
   * in Swing</A> for more information.
   */
  protected final synchronized void repaint() {
    if (component != null) {
      component.repaint(x, y, width, height);
    }
  }

  /**
   * Damages the area surrounding the caret to cause
   * it to be repainted in a new location.  If paint()
   * is reimplemented, this method should also be
   * reimplemented.  This method should update the
   * caret bounds (x, y, width, and height).
   *
   * @param r the current location of the caret
   * @see #paint
   */
  protected synchronized void damage(Rectangle r) {
    if (r != null) {
      int damageWidth = getCaretWidth(r.height);
      x = r.x - 4 - (damageWidth >> 1);
      y = r.y;
      width = 9 + damageWidth;
      height = r.height;
      repaint();
    }
  }

  /**
   * Scrolls the associated view (if necessary) to make
   * the caret visible.  Since how this should be done
   * is somewhat of a policy, this method can be
   * reimplemented to change the behavior.  By default
   * the scrollRectToVisible method is called on the
   * associated component.
   *
   * @param nloc the new position to scroll to
   */
  protected void adjustVisibility(Rectangle nloc) {
    if (component == null) {
      return;
    }
    if (SwingUtilities.isEventDispatchThread()) {
      component.scrollRectToVisible(nloc);
    } else {
      SwingUtilities.invokeLater(new SafeScroller(nloc));
    }
  }

  /**
   * Gets the painter for the Highlighter.
   *
   * @return the painter
   */
  protected Highlighter.HighlightPainter getSelectionPainter() {
    return DefaultHighlighter.DefaultPainter;
  }

  /**
   * Tries to set the position of the caret from
   * the coordinates of a mouse event, using viewToModel().
   *
   * @param e the mouse event
   */
  protected void positionCaret(MouseEvent e) {
    Point pt = new Point(e.getX(), e.getY());
    Position.Bias[] biasRet = new Position.Bias[1];
    int pos = component.getUI().viewToModel(component, pt, biasRet);
    if (biasRet[0] == null) {
      biasRet[0] = Position.Bias.Forward;
    }
    if (pos >= 0) {
      setDot(pos, biasRet[0]);
    }
  }

  /**
   * Tries to move the position of the caret from
   * the coordinates of a mouse event, using viewToModel().
   * This will cause a selection if the dot and mark
   * are different.
   *
   * @param e the mouse event
   */
  protected void moveCaret(MouseEvent e) {
    Point pt = new Point(e.getX(), e.getY());
    Position.Bias[] biasRet = new Position.Bias[1];
    int pos = component.getUI().viewToModel(component, pt, biasRet);
    if (biasRet[0] == null) {
      biasRet[0] = Position.Bias.Forward;
    }
    if (pos >= 0) {
      moveDot(pos, biasRet[0]);
    }
  }

  // --- FocusListener methods --------------------------

  /**
   * Called when the component containing the caret gains
   * focus.  This is implemented to set the caret to visible
   * if the component is editable.
   *
   * @param e the focus event
   * @see FocusListener#focusGained
   */
  public void focusGained(FocusEvent e) {
    if (component.isEnabled()) {
      if (component.isEditable()) {
        setVisible(true);
      }
      setSelectionVisible(true);
    }
  }

  /**
   * Called when the component containing the caret loses
   * focus.  This is implemented to set the caret to visibility
   * to false.
   *
   * @param e the focus event
   * @see FocusListener#focusLost
   */
  public void focusLost(FocusEvent e) {
    setVisible(false);
    setSelectionVisible(ownsSelection || e.isTemporary());
  }


  /**
   * Selects word based on the MouseEvent
   */
  private void selectWord(MouseEvent e) {
    if (selectedWordEvent != null
        && selectedWordEvent.getX() == e.getX()
        && selectedWordEvent.getY() == e.getY()) {
      //we already done selection for this
      return;
    }
    Action a = null;
    ActionMap map = getComponent().getActionMap();
    if (map != null) {
      a = map.get(DefaultEditorKit.selectWordAction);
    }
    if (a == null) {
      if (selectWord == null) {
        selectWord = new DefaultEditorKit.SelectWordAction();
      }
      a = selectWord;
    }
    a.actionPerformed(new ActionEvent(getComponent(),
        ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
    selectedWordEvent = e;
  }

  // --- MouseListener methods -----------------------------------

  /**
   * Called when the mouse is clicked.  If the click was generated
   * from button1, a double click selects a word,
   * and a triple click the current line.
   *
   * @param e the mouse event
   * @see MouseListener#mouseClicked
   */
  public void mouseClicked(MouseEvent e) {
    if (getComponent() == null) {
      return;
    }

    int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e);

    if (!e.isConsumed()) {
      if (SwingUtilities.isLeftMouseButton(e)) {
        // mouse 1 behavior
        if (nclicks == 1) {
          selectedWordEvent = null;
        } else if (nclicks == 2
            && SwingUtilities2.canEventAccessSystemClipboard(e)) {
          selectWord(e);
          selectedWordEvent = null;
        } else if (nclicks == 3
            && SwingUtilities2.canEventAccessSystemClipboard(e)) {
          Action a = null;
          ActionMap map = getComponent().getActionMap();
          if (map != null) {
            a = map.get(DefaultEditorKit.selectLineAction);
          }
          if (a == null) {
            if (selectLine == null) {
              selectLine = new DefaultEditorKit.SelectLineAction();
            }
            a = selectLine;
          }
          a.actionPerformed(new ActionEvent(getComponent(),
              ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
        }
      } else if (SwingUtilities.isMiddleMouseButton(e)) {
        // mouse 2 behavior
        if (nclicks == 1 && component.isEditable() && component.isEnabled()
            && SwingUtilities2.canEventAccessSystemClipboard(e)) {
          // paste system selection, if it exists
          JTextComponent c = (JTextComponent) e.getSource();
          if (c != null) {
            try {
              Toolkit tk = c.getToolkit();
              Clipboard buffer = tk.getSystemSelection();
              if (buffer != null) {
                // platform supports system selections, update it.
                adjustCaret(e);
                TransferHandler th = c.getTransferHandler();
                if (th != null) {
                  Transferable trans = null;

                  try {
                    trans = buffer.getContents(null);
                  } catch (IllegalStateException ise) {
                    // clipboard was unavailable
                    UIManager.getLookAndFeel().provideErrorFeedback(c);
                  }

                  if (trans != null) {
                    th.importData(c, trans);
                  }
                }
                adjustFocus(true);
              }
            } catch (HeadlessException he) {
              // do nothing... there is no system clipboard
            }
          }
        }
      }
    }
  }

  /**
   * If button 1 is pressed, this is implemented to
   * request focus on the associated text component,
   * and to set the caret position. If the shift key is held down,
   * the caret will be moved, potentially resulting in a selection,
   * otherwise the
   * caret position will be set to the new location.  If the component
   * is not enabled, there will be no request for focus.
   *
   * @param e the mouse event
   * @see MouseListener#mousePressed
   */
  public void mousePressed(MouseEvent e) {
    int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e);

    if (SwingUtilities.isLeftMouseButton(e)) {
      if (e.isConsumed()) {
        shouldHandleRelease = true;
      } else {
        shouldHandleRelease = false;
        adjustCaretAndFocus(e);
        if (nclicks == 2
            && SwingUtilities2.canEventAccessSystemClipboard(e)) {
          selectWord(e);
        }
      }
    }
  }

  void adjustCaretAndFocus(MouseEvent e) {
    adjustCaret(e);
    adjustFocus(false);
  }

  /**
   * Adjusts the caret location based on the MouseEvent.
   */
  private void adjustCaret(MouseEvent e) {
    if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 &&
        getDot() != -1) {
      moveCaret(e);
    } else if (!e.isPopupTrigger()) {
      positionCaret(e);
    }
  }

  /**
   * Adjusts the focus, if necessary.
   *
   * @param inWindow if true indicates requestFocusInWindow should be used
   */
  private void adjustFocus(boolean inWindow) {
    if ((component != null) && component.isEnabled() &&
        component.isRequestFocusEnabled()) {
      if (inWindow) {
        component.requestFocusInWindow();
      } else {
        component.requestFocus();
      }
    }
  }

  /**
   * Called when the mouse is released.
   *
   * @param e the mouse event
   * @see MouseListener#mouseReleased
   */
  public void mouseReleased(MouseEvent e) {
    if (!e.isConsumed()
        && shouldHandleRelease
        && SwingUtilities.isLeftMouseButton(e)) {

      adjustCaretAndFocus(e);
    }
  }

  /**
   * Called when the mouse enters a region.
   *
   * @param e the mouse event
   * @see MouseListener#mouseEntered
   */
  public void mouseEntered(MouseEvent e) {
  }

  /**
   * Called when the mouse exits a region.
   *
   * @param e the mouse event
   * @see MouseListener#mouseExited
   */
  public void mouseExited(MouseEvent e) {
  }

  // --- MouseMotionListener methods -------------------------

  /**
   * Moves the caret position
   * according to the mouse pointer's current
   * location.  This effectively extends the
   * selection.  By default, this is only done
   * for mouse button 1.
   *
   * @param e the mouse event
   * @see MouseMotionListener#mouseDragged
   */
  public void mouseDragged(MouseEvent e) {
    if ((!e.isConsumed()) && SwingUtilities.isLeftMouseButton(e)) {
      moveCaret(e);
    }
  }

  /**
   * Called when the mouse is moved.
   *
   * @param e the mouse event
   * @see MouseMotionListener#mouseMoved
   */
  public void mouseMoved(MouseEvent e) {
  }

  // ---- Caret methods ---------------------------------

  /**
   * Renders the caret as a vertical line.  If this is reimplemented
   * the damage method should also be reimplemented as it assumes the
   * shape of the caret is a vertical line.  Sets the caret color to
   * the value returned by getCaretColor().
   * <p>
   * If there are multiple text directions present in the associated
   * document, a flag indicating the caret bias will be rendered.
   * This will occur only if the associated document is a subclass
   * of AbstractDocument and there are multiple bidi levels present
   * in the bidi element structure (i.e. the text has multiple
   * directions associated with it).
   *
   * @param g the graphics context
   * @see #damage
   */
  public void paint(Graphics g) {
    if (isVisible()) {
      try {
        TextUI mapper = component.getUI();
        Rectangle r = mapper.modelToView(component, dot, dotBias);

        if ((r == null) || ((r.width == 0) && (r.height == 0))) {
          return;
        }
        if (width > 0 && height > 0 &&
            !this._contains(r.x, r.y, r.width, r.height)) {
          // We seem to have gotten out of sync and no longer
          // contain the right location, adjust accordingly.
          Rectangle clip = g.getClipBounds();

          if (clip != null && !clip.contains(this)) {
            // Clip doesn't contain the old location, force it
            // to be repainted lest we leave a caret around.
            repaint();
          }
          // This will potentially cause a repaint of something
          // we're already repainting, but without changing the
          // semantics of damage we can't really get around this.
          damage(r);
        }
        g.setColor(component.getCaretColor());
        int paintWidth = getCaretWidth(r.height);
        r.x -= paintWidth >> 1;
        g.fillRect(r.x, r.y, paintWidth, r.height);

        // see if we should paint a flag to indicate the bias
        // of the caret.
        // PENDING(prinz) this should be done through
        // protected methods so that alternative LAF
        // will show bidi information.
        Document doc = component.getDocument();
        if (doc instanceof AbstractDocument) {
          Element bidi = ((AbstractDocument) doc).getBidiRootElement();
          if ((bidi != null) && (bidi.getElementCount() > 1)) {
            // there are multiple directions present.
            flagXPoints[0] = r.x + ((dotLTR) ? paintWidth : 0);
            flagYPoints[0] = r.y;
            flagXPoints[1] = flagXPoints[0];
            flagYPoints[1] = flagYPoints[0] + 4;
            flagXPoints[2] = flagXPoints[0] + ((dotLTR) ? 4 : -4);
            flagYPoints[2] = flagYPoints[0];
            g.fillPolygon(flagXPoints, flagYPoints, 3);
          }
        }
      } catch (BadLocationException e) {
        // can't render I guess
        //System.err.println("Can't render cursor");
      }
    }
  }

  /**
   * Called when the UI is being installed into the
   * interface of a JTextComponent.  This can be used
   * to gain access to the model that is being navigated
   * by the implementation of this interface.  Sets the dot
   * and mark to 0, and establishes document, property change,
   * focus, mouse, and mouse motion listeners.
   *
   * @param c the component
   * @see Caret#install
   */
  public void install(JTextComponent c) {
    component = c;
    Document doc = c.getDocument();
    dot = mark = 0;
    dotLTR = markLTR = true;
    dotBias = markBias = Position.Bias.Forward;
    if (doc != null) {
      doc.addDocumentListener(handler);
    }
    c.addPropertyChangeListener(handler);
    c.addFocusListener(this);
    c.addMouseListener(this);
    c.addMouseMotionListener(this);

    // if the component already has focus, it won't
    // be notified.
    if (component.hasFocus()) {
      focusGained(null);
    }

    Number ratio = (Number) c.getClientProperty("caretAspectRatio");
    if (ratio != null) {
      aspectRatio = ratio.floatValue();
    } else {
      aspectRatio = -1;
    }

    Integer width = (Integer) c.getClientProperty("caretWidth");
    if (width != null) {
      caretWidth = width.intValue();
    } else {
      caretWidth = -1;
    }
  }

  /**
   * Called when the UI is being removed from the
   * interface of a JTextComponent.  This is used to
   * unregister any listeners that were attached.
   *
   * @param c the component
   * @see Caret#deinstall
   */
  public void deinstall(JTextComponent c) {
    c.removeMouseListener(this);
    c.removeMouseMotionListener(this);
    c.removeFocusListener(this);
    c.removePropertyChangeListener(handler);
    Document doc = c.getDocument();
    if (doc != null) {
      doc.removeDocumentListener(handler);
    }
    synchronized (this) {
      component = null;
    }
    if (flasher != null) {
      flasher.stop();
    }


  }

  /**
   * Adds a listener to track whenever the caret position has
   * been changed.
   *
   * @param l the listener
   * @see Caret#addChangeListener
   */
  public void addChangeListener(ChangeListener l) {
    listenerList.add(ChangeListener.class, l);
  }

  /**
   * Removes a listener that was tracking caret position changes.
   *
   * @param l the listener
   * @see Caret#removeChangeListener
   */
  public void removeChangeListener(ChangeListener l) {
    listenerList.remove(ChangeListener.class, l);
  }

  /**
   * Returns an array of all the change listeners
   * registered on this caret.
   *
   * @return all of this caret's <code>ChangeListener</code>s or an empty array if no change
   * listeners are currently registered
   * @see #addChangeListener
   * @see #removeChangeListener
   * @since 1.4
   */
  public ChangeListener[] getChangeListeners() {
    return listenerList.getListeners(ChangeListener.class);
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.  The event instance
   * is lazily created using the parameters passed into
   * the fire method.  The listener list is processed last to first.
   *
   * @see EventListenerList
   */
  protected void fireStateChanged() {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == ChangeListener.class) {
        // Lazily create the event:
        if (changeEvent == null) {
          changeEvent = new ChangeEvent(this);
        }
        ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent);
      }
    }
  }

  /**
   * Returns an array of all the objects currently registered
   * as <code><em>Foo</em>Listener</code>s
   * upon this caret.
   * <code><em>Foo</em>Listener</code>s are registered using the
   * <code>add<em>Foo</em>Listener</code> method.
   *
   * <p>
   *
   * You can specify the <code>listenerType</code> argument
   * with a class literal,
   * such as
   * <code><em>Foo</em>Listener.class</code>.
   * For example, you can query a
   * <code>DefaultCaret</code> <code>c</code>
   * for its change listeners with the following code:
   *
   * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));</pre>
   *
   * If no such listeners exist, this method returns an empty array.
   *
   * @param listenerType the type of listeners requested; this parameter should specify an interface
   * that descends from <code>java.util.EventListener</code>
   * @return an array of all objects registered as <code><em>Foo</em>Listener</code>s on this
   * component, or an empty array if no such listeners have been added
   * @throws ClassCastException if <code>listenerType</code> doesn't specify a class or interface
   * that implements <code>java.util.EventListener</code>
   * @see #getChangeListeners
   * @since 1.3
   */
  public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
    return listenerList.getListeners(listenerType);
  }

  /**
   * Changes the selection visibility.
   *
   * @param vis the new visibility
   */
  public void setSelectionVisible(boolean vis) {
    if (vis != selectionVisible) {
      selectionVisible = vis;
      if (selectionVisible) {
        // show
        Highlighter h = component.getHighlighter();
        if ((dot != mark) && (h != null) && (selectionTag == null)) {
          int p0 = Math.min(dot, mark);
          int p1 = Math.max(dot, mark);
          Highlighter.HighlightPainter p = getSelectionPainter();
          try {
            selectionTag = h.addHighlight(p0, p1, p);
          } catch (BadLocationException bl) {
            selectionTag = null;
          }
        }
      } else {
        // hide
        if (selectionTag != null) {
          Highlighter h = component.getHighlighter();
          h.removeHighlight(selectionTag);
          selectionTag = null;
        }
      }
    }
  }

  /**
   * Checks whether the current selection is visible.
   *
   * @return true if the selection is visible
   */
  public boolean isSelectionVisible() {
    return selectionVisible;
  }

  /**
   * Determines if the caret is currently active.
   * <p>
   * This method returns whether or not the <code>Caret</code>
   * is currently in a blinking state. It does not provide
   * information as to whether it is currently blinked on or off.
   * To determine if the caret is currently painted use the
   * <code>isVisible</code> method.
   *
   * @return <code>true</code> if active else <code>false</code>
   * @see #isVisible
   * @since 1.5
   */
  public boolean isActive() {
    return active;
  }

  /**
   * Indicates whether or not the caret is currently visible. As the
   * caret flashes on and off the return value of this will change
   * between true, when the caret is painted, and false, when the
   * caret is not painted. <code>isActive</code> indicates whether
   * or not the caret is in a blinking state, such that it <b>can</b>
   * be visible, and <code>isVisible</code> indicates whether or not
   * the caret <b>is</b> actually visible.
   * <p>
   * Subclasses that wish to render a different flashing caret
   * should override paint and only paint the caret if this method
   * returns true.
   *
   * @return true if visible else false
   * @see Caret#isVisible
   * @see #isActive
   */
  public boolean isVisible() {
    return visible;
  }

  /**
   * Sets the caret visibility, and repaints the caret.
   * It is important to understand the relationship between this method,
   * <code>isVisible</code> and <code>isActive</code>.
   * Calling this method with a value of <code>true</code> activates the
   * caret blinking. Setting it to <code>false</code> turns it completely off.
   * To determine whether the blinking is active, you should call
   * <code>isActive</code>. In effect, <code>isActive</code> is an
   * appropriate corresponding "getter" method for this one.
   * <code>isVisible</code> can be used to fetch the current
   * visibility status of the caret, meaning whether or not it is currently
   * painted. This status will change as the caret blinks on and off.
   * <p>
   * Here's a list showing the potential return values of both
   * <code>isActive</code> and <code>isVisible</code>
   * after calling this method:
   * <p>
   * <b><code>setVisible(true)</code></b>:
   * <ul>
   * <li>isActive(): true</li>
   * <li>isVisible(): true or false depending on whether
   * or not the caret is blinked on or off</li>
   * </ul>
   * <p>
   * <b><code>setVisible(false)</code></b>:
   * <ul>
   * <li>isActive(): false</li>
   * <li>isVisible(): false</li>
   * </ul>
   *
   * @param e the visibility specifier
   * @see #isActive
   * @see Caret#setVisible
   */
  public void setVisible(boolean e) {
    // focus lost notification can come in later after the
    // caret has been deinstalled, in which case the component
    // will be null.
    active = e;
    if (component != null) {
      TextUI mapper = component.getUI();
      if (visible != e) {
        visible = e;
        // repaint the caret
        try {
          Rectangle loc = mapper.modelToView(component, dot, dotBias);
          damage(loc);
        } catch (BadLocationException badloc) {
          // hmm... not legally positioned
        }
      }
    }
    if (flasher != null) {
      if (visible) {
        flasher.start();
      } else {
        flasher.stop();
      }
    }
  }

  /**
   * Sets the caret blink rate.
   *
   * @param rate the rate in milliseconds, 0 to stop blinking
   * @see Caret#setBlinkRate
   */
  public void setBlinkRate(int rate) {
    if (rate != 0) {
      if (flasher == null) {
        flasher = new Timer(rate, handler);
      }
      flasher.setDelay(rate);
    } else {
      if (flasher != null) {
        flasher.stop();
        flasher.removeActionListener(handler);
        flasher = null;
      }
    }
  }

  /**
   * Gets the caret blink rate.
   *
   * @return the delay in milliseconds.  If this is zero the caret will not blink.
   * @see Caret#getBlinkRate
   */
  public int getBlinkRate() {
    return (flasher == null) ? 0 : flasher.getDelay();
  }

  /**
   * Fetches the current position of the caret.
   *
   * @return the position &gt;= 0
   * @see Caret#getDot
   */
  public int getDot() {
    return dot;
  }

  /**
   * Fetches the current position of the mark.  If there is a selection,
   * the dot and mark will not be the same.
   *
   * @return the position &gt;= 0
   * @see Caret#getMark
   */
  public int getMark() {
    return mark;
  }

  /**
   * Sets the caret position and mark to the specified position,
   * with a forward bias. This implicitly sets the
   * selection range to zero.
   *
   * @param dot the position &gt;= 0
   * @see #setDot(int, Position.Bias)
   * @see Caret#setDot
   */
  public void setDot(int dot) {
    setDot(dot, Position.Bias.Forward);
  }

  /**
   * Moves the caret position to the specified position,
   * with a forward bias.
   *
   * @param dot the position &gt;= 0
   * @see #moveDot(int, javax.swing.text.Position.Bias)
   * @see Caret#moveDot
   */
  public void moveDot(int dot) {
    moveDot(dot, Position.Bias.Forward);
  }

  // ---- Bidi methods (we could put these in a subclass)

  /**
   * Moves the caret position to the specified position, with the
   * specified bias.
   *
   * @param dot the position &gt;= 0
   * @param dotBias the bias for this position, not <code>null</code>
   * @throws IllegalArgumentException if the bias is <code>null</code>
   * @see Caret#moveDot
   * @since 1.6
   */
  public void moveDot(int dot, Position.Bias dotBias) {
    if (dotBias == null) {
      throw new IllegalArgumentException("null bias");
    }

    if (!component.isEnabled()) {
      // don't allow selection on disabled components.
      setDot(dot, dotBias);
      return;
    }
    if (dot != this.dot) {
      NavigationFilter filter = component.getNavigationFilter();

      if (filter != null) {
        filter.moveDot(getFilterBypass(), dot, dotBias);
      } else {
        handleMoveDot(dot, dotBias);
      }
    }
  }

  void handleMoveDot(int dot, Position.Bias dotBias) {
    changeCaretPosition(dot, dotBias);

    if (selectionVisible) {
      Highlighter h = component.getHighlighter();
      if (h != null) {
        int p0 = Math.min(dot, mark);
        int p1 = Math.max(dot, mark);

        // if p0 == p1 then there should be no highlight, remove it if necessary
        if (p0 == p1) {
          if (selectionTag != null) {
            h.removeHighlight(selectionTag);
            selectionTag = null;
          }
          // otherwise, change or add the highlight
        } else {
          try {
            if (selectionTag != null) {
              h.changeHighlight(selectionTag, p0, p1);
            } else {
              Highlighter.HighlightPainter p = getSelectionPainter();
              selectionTag = h.addHighlight(p0, p1, p);
            }
          } catch (BadLocationException e) {
            throw new StateInvariantError("Bad caret position");
          }
        }
      }
    }
  }

  /**
   * Sets the caret position and mark to the specified position, with the
   * specified bias. This implicitly sets the selection range
   * to zero.
   *
   * @param dot the position &gt;= 0
   * @param dotBias the bias for this position, not <code>null</code>
   * @throws IllegalArgumentException if the bias is <code>null</code>
   * @see Caret#setDot
   * @since 1.6
   */
  public void setDot(int dot, Position.Bias dotBias) {
    if (dotBias == null) {
      throw new IllegalArgumentException("null bias");
    }

    NavigationFilter filter = component.getNavigationFilter();

    if (filter != null) {
      filter.setDot(getFilterBypass(), dot, dotBias);
    } else {
      handleSetDot(dot, dotBias);
    }
  }

  void handleSetDot(int dot, Position.Bias dotBias) {
    // move dot, if it changed
    Document doc = component.getDocument();
    if (doc != null) {
      dot = Math.min(dot, doc.getLength());
    }
    dot = Math.max(dot, 0);

    // The position (0,Backward) is out of range so disallow it.
    if (dot == 0) {
      dotBias = Position.Bias.Forward;
    }

    mark = dot;
    if (this.dot != dot || this.dotBias != dotBias ||
        selectionTag != null || forceCaretPositionChange) {
      changeCaretPosition(dot, dotBias);
    }
    this.markBias = this.dotBias;
    this.markLTR = dotLTR;
    Highlighter h = component.getHighlighter();
    if ((h != null) && (selectionTag != null)) {
      h.removeHighlight(selectionTag);
      selectionTag = null;
    }
  }

  /**
   * Returns the bias of the caret position.
   *
   * @return the bias of the caret position
   * @since 1.6
   */
  public Position.Bias getDotBias() {
    return dotBias;
  }

  /**
   * Returns the bias of the mark.
   *
   * @return the bias of the mark
   * @since 1.6
   */
  public Position.Bias getMarkBias() {
    return markBias;
  }

  boolean isDotLeftToRight() {
    return dotLTR;
  }

  boolean isMarkLeftToRight() {
    return markLTR;
  }

  boolean isPositionLTR(int position, Position.Bias bias) {
    Document doc = component.getDocument();
    if (bias == Position.Bias.Backward && --position < 0) {
      position = 0;
    }
    return AbstractDocument.isLeftToRight(doc, position, position);
  }

  Position.Bias guessBiasForOffset(int offset, Position.Bias lastBias,
      boolean lastLTR) {
    // There is an abiguous case here. That if your model looks like:
    // abAB with the cursor at abB]A (visual representation of
    // 3 forward) deleting could either become abB] or
    // ab[B. I'ld actually prefer abB]. But, if I implement that
    // a delete at abBA] would result in aBA] vs a[BA which I
    // think is totally wrong. To get this right we need to know what
    // was deleted. And we could get this from the bidi structure
    // in the change event. So:
    // PENDING: base this off what was deleted.
    if (lastLTR != isPositionLTR(offset, lastBias)) {
      lastBias = Position.Bias.Backward;
    } else if (lastBias != Position.Bias.Backward &&
        lastLTR != isPositionLTR(offset, Position.Bias.Backward)) {
      lastBias = Position.Bias.Backward;
    }
    if (lastBias == Position.Bias.Backward && offset > 0) {
      try {
        Segment s = new Segment();
        component.getDocument().getText(offset - 1, 1, s);
        if (s.count > 0 && s.array[s.offset] == '\n') {
          lastBias = Position.Bias.Forward;
        }
      } catch (BadLocationException ble) {
      }
    }
    return lastBias;
  }

  // ---- local methods --------------------------------------------

  /**
   * Sets the caret position (dot) to a new location.  This
   * causes the old and new location to be repainted.  It
   * also makes sure that the caret is within the visible
   * region of the view, if the view is scrollable.
   */
  void changeCaretPosition(int dot, Position.Bias dotBias) {
    // repaint the old position and set the new value of
    // the dot.
    repaint();

    // Make sure the caret is visible if this window has the focus.
    if (flasher != null && flasher.isRunning()) {
      visible = true;
      flasher.restart();
    }

    // notify listeners at the caret moved
    this.dot = dot;
    this.dotBias = dotBias;
    dotLTR = isPositionLTR(dot, dotBias);
    fireStateChanged();

    updateSystemSelection();

    setMagicCaretPosition(null);

    // We try to repaint the caret later, since things
    // may be unstable at the time this is called
    // (i.e. we don't want to depend upon notification
    // order or the fact that this might happen on
    // an unsafe thread).
    Runnable callRepaintNewCaret = new Runnable() {
      public void run() {
        repaintNewCaret();
      }
    };
    SwingUtilities.invokeLater(callRepaintNewCaret);
  }

  /**
   * Repaints the new caret position, with the
   * assumption that this is happening on the
   * event thread so that calling <code>modelToView</code>
   * is safe.
   */
  void repaintNewCaret() {
    if (component != null) {
      TextUI mapper = component.getUI();
      Document doc = component.getDocument();
      if ((mapper != null) && (doc != null)) {
        // determine the new location and scroll if
        // not visible.
        Rectangle newLoc;
        try {
          newLoc = mapper.modelToView(component, this.dot, this.dotBias);
        } catch (BadLocationException e) {
          newLoc = null;
        }
        if (newLoc != null) {
          adjustVisibility(newLoc);
          // If there is no magic caret position, make one
          if (getMagicCaretPosition() == null) {
            setMagicCaretPosition(new Point(newLoc.x, newLoc.y));
          }
        }

        // repaint the new position
        damage(newLoc);
      }
    }
  }

  private void updateSystemSelection() {
    if (!SwingUtilities2.canCurrentEventAccessSystemClipboard()) {
      return;
    }
    if (this.dot != this.mark && component != null && component.hasFocus()) {
      Clipboard clip = getSystemSelection();
      if (clip != null) {
        String selectedText;
        if (component instanceof JPasswordField
            && component.getClientProperty("JPasswordField.cutCopyAllowed") !=
            Boolean.TRUE) {
          //fix for 4793761
          StringBuilder txt = null;
          char echoChar = ((JPasswordField) component).getEchoChar();
          int p0 = Math.min(getDot(), getMark());
          int p1 = Math.max(getDot(), getMark());
          for (int i = p0; i < p1; i++) {
            if (txt == null) {
              txt = new StringBuilder();
            }
            txt.append(echoChar);
          }
          selectedText = (txt != null) ? txt.toString() : null;
        } else {
          selectedText = component.getSelectedText();
        }
        try {
          clip.setContents(
              new StringSelection(selectedText), getClipboardOwner());

          ownsSelection = true;
        } catch (IllegalStateException ise) {
          // clipboard was unavailable
          // no need to provide error feedback to user since updating
          // the system selection is not a user invoked action
        }
      }
    }
  }

  private Clipboard getSystemSelection() {
    try {
      return component.getToolkit().getSystemSelection();
    } catch (HeadlessException he) {
      // do nothing... there is no system clipboard
    } catch (SecurityException se) {
      // do nothing... there is no allowed system clipboard
    }
    return null;
  }

  private ClipboardOwner getClipboardOwner() {
    return handler;
  }

  /**
   * This is invoked after the document changes to verify the current
   * dot/mark is valid. We do this in case the <code>NavigationFilter</code>
   * changed where to position the dot, that resulted in the current location
   * being bogus.
   */
  private void ensureValidPosition() {
    int length = component.getDocument().getLength();
    if (dot > length || mark > length) {
      // Current location is bogus and filter likely vetoed the
      // change, force the reset without giving the filter a
      // chance at changing it.
      handleSetDot(length, Position.Bias.Forward);
    }
  }


  /**
   * Saves the current caret position.  This is used when
   * caret up/down actions occur, moving between lines
   * that have uneven end positions.
   *
   * @param p the position
   * @see #getMagicCaretPosition
   */
  public void setMagicCaretPosition(Point p) {
    magicCaretPosition = p;
  }

  /**
   * Gets the saved caret position.
   *
   * @return the position see #setMagicCaretPosition
   */
  public Point getMagicCaretPosition() {
    return magicCaretPosition;
  }

  /**
   * Compares this object to the specified object.
   * The superclass behavior of comparing rectangles
   * is not desired, so this is changed to the Object
   * behavior.
   *
   * @param obj the object to compare this font with
   * @return <code>true</code> if the objects are equal; <code>false</code> otherwise
   */
  public boolean equals(Object obj) {
    return (this == obj);
  }

  public String toString() {
    String s = "Dot=(" + dot + ", " + dotBias + ")";
    s += " Mark=(" + mark + ", " + markBias + ")";
    return s;
  }

  private NavigationFilter.FilterBypass getFilterBypass() {
    if (filterBypass == null) {
      filterBypass = new DefaultFilterBypass();
    }
    return filterBypass;
  }

  // Rectangle.contains returns false if passed a rect with a w or h == 0,
  // this won't (assuming X,Y are contained with this rectangle).
  private boolean _contains(int X, int Y, int W, int H) {
    int w = this.width;
    int h = this.height;
    if ((w | h | W | H) < 0) {
      // At least one of the dimensions is negative...
      return false;
    }
    // Note: if any dimension is zero, tests below must return false...
    int x = this.x;
    int y = this.y;
    if (X < x || Y < y) {
      return false;
    }
    if (W > 0) {
      w += x;
      W += X;
      if (W <= X) {
        // X+W overflowed or W was zero, return false if...
        // either original w or W was zero or
        // x+w did not overflow or
        // the overflowed x+w is smaller than the overflowed X+W
        if (w >= x || W > w) {
          return false;
        }
      } else {
        // X+W did not overflow and W was not zero, return false if...
        // original w was zero or
        // x+w did not overflow and x+w is smaller than X+W
        if (w >= x && W > w) {
          return false;
        }
      }
    } else if ((x + w) < X) {
      return false;
    }
    if (H > 0) {
      h += y;
      H += Y;
      if (H <= Y) {
        if (h >= y || H > h) {
          return false;
        }
      } else {
        if (h >= y && H > h) {
          return false;
        }
      }
    } else if ((y + h) < Y) {
      return false;
    }
    return true;
  }

  int getCaretWidth(int height) {
    if (aspectRatio > -1) {
      return (int) (aspectRatio * height) + 1;
    }

    if (caretWidth > -1) {
      return caretWidth;
    } else {
      Object property = UIManager.get("Caret.width");
      if (property instanceof Integer) {
        return ((Integer) property).intValue();
      } else {
        return 1;
      }
    }
  }

  // --- serialization ---------------------------------------------

  private void readObject(ObjectInputStream s)
      throws ClassNotFoundException, IOException {
    s.defaultReadObject();
    handler = new Handler();
    if (!s.readBoolean()) {
      dotBias = Position.Bias.Forward;
    } else {
      dotBias = Position.Bias.Backward;
    }
    if (!s.readBoolean()) {
      markBias = Position.Bias.Forward;
    } else {
      markBias = Position.Bias.Backward;
    }
  }

  private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();
    s.writeBoolean((dotBias == Position.Bias.Backward));
    s.writeBoolean((markBias == Position.Bias.Backward));
  }

  // ---- member variables ------------------------------------------

  /**
   * The event listener list.
   */
  protected EventListenerList listenerList = new EventListenerList();

  /**
   * The change event for the model.
   * Only one ChangeEvent is needed per model instance since the
   * event's only (read-only) state is the source property.  The source
   * of events generated here is always "this".
   */
  protected transient ChangeEvent changeEvent = null;

  // package-private to avoid inner classes private member
  // access bug
  JTextComponent component;

  int updatePolicy = UPDATE_WHEN_ON_EDT;
  boolean visible;
  boolean active;
  int dot;
  int mark;
  Object selectionTag;
  boolean selectionVisible;
  Timer flasher;
  Point magicCaretPosition;
  transient Position.Bias dotBias;
  transient Position.Bias markBias;
  boolean dotLTR;
  boolean markLTR;
  transient Handler handler = new Handler();
  transient private int[] flagXPoints = new int[3];
  transient private int[] flagYPoints = new int[3];
  private transient NavigationFilter.FilterBypass filterBypass;
  static private transient Action selectWord = null;
  static private transient Action selectLine = null;
  /**
   * This is used to indicate if the caret currently owns the selection.
   * This is always false if the system does not support the system
   * clipboard.
   */
  private boolean ownsSelection;

  /**
   * If this is true, the location of the dot is updated regardless of
   * the current location. This is set in the DocumentListener
   * such that even if the model location of dot hasn't changed (perhaps do
   * to a forward delete) the visual location is updated.
   */
  private boolean forceCaretPositionChange;

  /**
   * Whether or not mouseReleased should adjust the caret and focus.
   * This flag is set by mousePressed if it wanted to adjust the caret
   * and focus but couldn't because of a possible DnD operation.
   */
  private transient boolean shouldHandleRelease;


  /**
   * holds last MouseEvent which caused the word selection
   */
  private transient MouseEvent selectedWordEvent = null;

  /**
   * The width of the caret in pixels.
   */
  private int caretWidth = -1;
  private float aspectRatio = -1;

  class SafeScroller implements Runnable {

    SafeScroller(Rectangle r) {
      this.r = r;
    }

    public void run() {
      if (component != null) {
        component.scrollRectToVisible(r);
      }
    }

    Rectangle r;
  }


  class Handler implements PropertyChangeListener, DocumentListener, ActionListener,
      ClipboardOwner {

    // --- ActionListener methods ----------------------------------

    /**
     * Invoked when the blink timer fires.  This is called
     * asynchronously.  The simply changes the visibility
     * and repaints the rectangle that last bounded the caret.
     *
     * @param e the action event
     */
    public void actionPerformed(ActionEvent e) {
      if (width == 0 || height == 0) {
        // setVisible(true) will cause a scroll, only do this if the
        // new location is really valid.
        if (component != null) {
          TextUI mapper = component.getUI();
          try {
            Rectangle r = mapper.modelToView(component, dot,
                dotBias);
            if (r != null && r.width != 0 && r.height != 0) {
              damage(r);
            }
          } catch (BadLocationException ble) {
          }
        }
      }
      visible = !visible;
      repaint();
    }

    // --- DocumentListener methods --------------------------------

    /**
     * Updates the dot and mark if they were changed by
     * the insertion.
     *
     * @param e the document event
     * @see DocumentListener#insertUpdate
     */
    public void insertUpdate(DocumentEvent e) {
      if (getUpdatePolicy() == NEVER_UPDATE ||
          (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
              !SwingUtilities.isEventDispatchThread())) {

        if ((e.getOffset() <= dot || e.getOffset() <= mark)
            && selectionTag != null) {
          try {
            component.getHighlighter().changeHighlight(selectionTag,
                Math.min(dot, mark), Math.max(dot, mark));
          } catch (BadLocationException e1) {
            e1.printStackTrace();
          }
        }
        return;
      }
      int offset = e.getOffset();
      int length = e.getLength();
      int newDot = dot;
      short changed = 0;

      if (e instanceof AbstractDocument.UndoRedoDocumentEvent) {
        setDot(offset + length);
        return;
      }
      if (newDot >= offset) {
        newDot += length;
        changed |= 1;
      }
      int newMark = mark;
      if (newMark >= offset) {
        newMark += length;
        changed |= 2;
      }

      if (changed != 0) {
        Position.Bias dotBias = DefaultCaret.this.dotBias;
        if (dot == offset) {
          Document doc = component.getDocument();
          boolean isNewline;
          try {
            Segment s = new Segment();
            doc.getText(newDot - 1, 1, s);
            isNewline = (s.count > 0 &&
                s.array[s.offset] == '\n');
          } catch (BadLocationException ble) {
            isNewline = false;
          }
          if (isNewline) {
            dotBias = Position.Bias.Forward;
          } else {
            dotBias = Position.Bias.Backward;
          }
        }
        if (newMark == newDot) {
          setDot(newDot, dotBias);
          ensureValidPosition();
        } else {
          setDot(newMark, markBias);
          if (getDot() == newMark) {
            // Due this test in case the filter vetoed the
            // change in which case this probably won't be
            // valid either.
            moveDot(newDot, dotBias);
          }
          ensureValidPosition();
        }
      }
    }

    /**
     * Updates the dot and mark if they were changed
     * by the removal.
     *
     * @param e the document event
     * @see DocumentListener#removeUpdate
     */
    public void removeUpdate(DocumentEvent e) {
      if (getUpdatePolicy() == NEVER_UPDATE ||
          (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
              !SwingUtilities.isEventDispatchThread())) {

        int length = component.getDocument().getLength();
        dot = Math.min(dot, length);
        mark = Math.min(mark, length);
        if ((e.getOffset() < dot || e.getOffset() < mark)
            && selectionTag != null) {
          try {
            component.getHighlighter().changeHighlight(selectionTag,
                Math.min(dot, mark), Math.max(dot, mark));
          } catch (BadLocationException e1) {
            e1.printStackTrace();
          }
        }
        return;
      }
      int offs0 = e.getOffset();
      int offs1 = offs0 + e.getLength();
      int newDot = dot;
      boolean adjustDotBias = false;
      int newMark = mark;
      boolean adjustMarkBias = false;

      if (e instanceof AbstractDocument.UndoRedoDocumentEvent) {
        setDot(offs0);
        return;
      }
      if (newDot >= offs1) {
        newDot -= (offs1 - offs0);
        if (newDot == offs1) {
          adjustDotBias = true;
        }
      } else if (newDot >= offs0) {
        newDot = offs0;
        adjustDotBias = true;
      }
      if (newMark >= offs1) {
        newMark -= (offs1 - offs0);
        if (newMark == offs1) {
          adjustMarkBias = true;
        }
      } else if (newMark >= offs0) {
        newMark = offs0;
        adjustMarkBias = true;
      }
      if (newMark == newDot) {
        forceCaretPositionChange = true;
        try {
          setDot(newDot, guessBiasForOffset(newDot, dotBias,
              dotLTR));
        } finally {
          forceCaretPositionChange = false;
        }
        ensureValidPosition();
      } else {
        Position.Bias dotBias = DefaultCaret.this.dotBias;
        Position.Bias markBias = DefaultCaret.this.markBias;
        if (adjustDotBias) {
          dotBias = guessBiasForOffset(newDot, dotBias, dotLTR);
        }
        if (adjustMarkBias) {
          markBias = guessBiasForOffset(mark, markBias, markLTR);
        }
        setDot(newMark, markBias);
        if (getDot() == newMark) {
          // Due this test in case the filter vetoed the change
          // in which case this probably won't be valid either.
          moveDot(newDot, dotBias);
        }
        ensureValidPosition();
      }
    }

    /**
     * Gives notification that an attribute or set of attributes changed.
     *
     * @param e the document event
     * @see DocumentListener#changedUpdate
     */
    public void changedUpdate(DocumentEvent e) {
      if (getUpdatePolicy() == NEVER_UPDATE ||
          (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
              !SwingUtilities.isEventDispatchThread())) {
        return;
      }
      if (e instanceof AbstractDocument.UndoRedoDocumentEvent) {
        setDot(e.getOffset() + e.getLength());
      }
    }

    // --- PropertyChangeListener methods -----------------------

    /**
     * This method gets called when a bound property is changed.
     * We are looking for document changes on the editor.
     */
    public void propertyChange(PropertyChangeEvent evt) {
      Object oldValue = evt.getOldValue();
      Object newValue = evt.getNewValue();
      if ((oldValue instanceof Document) || (newValue instanceof Document)) {
        setDot(0);
        if (oldValue != null) {
          ((Document) oldValue).removeDocumentListener(this);
        }
        if (newValue != null) {
          ((Document) newValue).addDocumentListener(this);
        }
      } else if ("enabled".equals(evt.getPropertyName())) {
        Boolean enabled = (Boolean) evt.getNewValue();
        if (component.isFocusOwner()) {
          if (enabled == Boolean.TRUE) {
            if (component.isEditable()) {
              setVisible(true);
            }
            setSelectionVisible(true);
          } else {
            setVisible(false);
            setSelectionVisible(false);
          }
        }
      } else if ("caretWidth".equals(evt.getPropertyName())) {
        Integer newWidth = (Integer) evt.getNewValue();
        if (newWidth != null) {
          caretWidth = newWidth.intValue();
        } else {
          caretWidth = -1;
        }
        repaint();
      } else if ("caretAspectRatio".equals(evt.getPropertyName())) {
        Number newRatio = (Number) evt.getNewValue();
        if (newRatio != null) {
          aspectRatio = newRatio.floatValue();
        } else {
          aspectRatio = -1;
        }
        repaint();
      }
    }

    //
    // ClipboardOwner
    //

    /**
     * Toggles the visibility of the selection when ownership is lost.
     */
    public void lostOwnership(Clipboard clipboard,
        Transferable contents) {
      if (ownsSelection) {
        ownsSelection = false;
        if (component != null && !component.hasFocus()) {
          setSelectionVisible(false);
        }
      }
    }
  }


  private class DefaultFilterBypass extends NavigationFilter.FilterBypass {

    public Caret getCaret() {
      return DefaultCaret.this;
    }

    public void setDot(int dot, Position.Bias bias) {
      handleSetDot(dot, bias);
    }

    public void moveDot(int dot, Position.Bias bias) {
      handleMoveDot(dot, bias);
    }
  }
}
